package com.aepryus.loom;

import java.sql.*;
import java.util.*;
import java.util.Date;
import com.aepryus.util.*;

abstract public class Persistence {
	private Pool pool;
	private Logger logger;

    public abstract String toSQL (String value);
	public abstract String toSQL (Date value);
	public abstract Date toDate (Object value);
	public abstract String toSQL (com.aepryus.util.DateTime value);
	public abstract DateTime toDateTime (Object value);

	public Persistence (String dbClass,String dbURL,String username,String password) throws PersistenceException {
		try {

			pool = new Pool(dbClass,dbURL,username,password,8);

		} catch (ClassNotFoundException e) {
		    throw new PersistenceException("Unable to find database's JDBC drivers.  Make sure the drivers are in the classpath.");
		} catch (SQLException e) {
			throw new PersistenceException("Unable to connect to the database.  Make sure it is running.");
		}
	}

	public void setLogger (Logger logger) {this.logger = logger;}

	public Map<String,Object>[] query (String query) throws PersistenceException {
		return query(query,(String[])null);
	}
	public Matrix query (String key,String query) throws PersistenceException {
		return new Matrix(key,query(query));
	}
	public Matrix query (String key,String query,String[] names) throws PersistenceException {
		return new Matrix(key,query(query,names));
	}
	public long queryLong (String query) throws PersistenceException {
		Map[] maps = query(query,new String[]{"value"});
		long value=0;
		for (Map map : maps) {
			if (map.get("value") != null)
				value += ((Number) map.get("value")).longValue();
		}
		return value;
	}
	public double queryDouble (String query) throws PersistenceException {
		Map[] maps = query(query,new String[]{"value"});
		double value=0;
		for (Map map : maps) {
			if (map.get("value") != null)
				value += ((Number) map.get("value")).doubleValue();
		}
		return value;
	}
	public Date queryDate (String query) throws PersistenceException {
		Map[] maps = query(query,new String[]{"value"});
		return (Date)maps[0].get("value");
	}

	@SuppressWarnings("unchecked")
	public Map<String,Object>[] query (String query,String[] names) throws PersistenceException {
		Connection connection = null;
		try {
			connection = pool.shared();
			Statement s = connection.createStatement();
			ResultSet rs = s.executeQuery(query);

			List<Map<String,Object>> list = new Vector<Map<String,Object>>();
			while (rs.next())
				list.add(load(rs,names));

			rs.close();
			s.close();

			return list.toArray(new Map[]{});

		} catch (SQLException e) {
			if (e.getSQLState().equals("08S01") || e.getErrorCode() == 17002 || e.getErrorCode() == 17401 || e.getErrorCode() == 17412 || e.getErrorCode() == 17442) {
				try {
				    Log.print("Refreshing Shared Connection::("+e.getErrorCode()+")");
					pool.refresh(connection);
				} catch (SQLException e2) {
					SQL.setPersistence(null);
					throw new PersistenceException("Database appears to have gone offline.");
				}
				return query(query,names);
			} else
			    throw new PersistenceException(e,query);
		}
	}
	public int update (String update) throws PersistenceException {
		if (logger != null) logger.print(update);
		try {
			Connection connection = pool.exclusive();
			Statement s = connection.createStatement();
	    	int result = s.executeUpdate(update);
            s.close();
			return result;
		} catch (SQLException e) {
            if (e.getSQLState().equals("08S01") || e.getErrorCode() == 17002 || e.getErrorCode() == 17401 || e.getErrorCode() == 17412 || e.getErrorCode() == 17442) {
				try {
				    Log.print("Refreshing Exclusive Connection::("+e.getErrorCode()+")");
					pool.refresh();
				} catch (SQLException e2) {
					SQL.setPersistence(null);
					throw new PersistenceException("Database appears to have gone offline.");
				}
				return update(update);
			} else
			    throw new PersistenceException(e,update);
		}
	}

	public void commit () throws PersistenceException {
		try {
			Connection connection = pool.exclusive();
			connection.commit();
			pool.release();
		} catch (SQLException e) {
		    throw new PersistenceException(e);
		}
	}
	public void rollback () throws PersistenceException {
		try {
			Connection connection = pool.exclusive();
			connection.rollback();
			pool.release();
		} catch (SQLException e) {
		    throw new PersistenceException(e);
		}
	}
	public void close () throws PersistenceException {
		try {
			pool.close();
			pool = null;
		} catch (SQLException e) {
			throw new PersistenceException(e);
		}
	}

// Object ======================================================================
	public void finalize () throws Throwable {
		super.finalize();
		close();
	}

// Static ======================================================================
	private static Map<String,Object> load (ResultSet rs,String[] names) throws SQLException {
		Map<String,Object> map = new HashMap<String,Object>();

		ResultSetMetaData rsmd = rs.getMetaData();

		for (int i=0;i<rsmd.getColumnCount();i++) {
		    String name = rsmd.getColumnName(i+1);
		    map.put(names==null?name.toUpperCase():names[i],rs.getObject(name));
		}

		return map;
	}
}

